package com.doyens.gmall.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.doyens.gmall.common.cache.GmallCache;
import com.doyens.gmall.common.constant.RedisConst;
import com.doyens.gmall.common.result.Result;
import com.doyens.gmall.model.product.SkuAttrValue;
import com.doyens.gmall.model.product.SkuImage;
import com.doyens.gmall.model.product.SkuInfo;
import com.doyens.gmall.model.product.SkuSaleAttrValue;
import com.doyens.gmall.product.mapper.SkuAttrValueMapper;
import com.doyens.gmall.product.mapper.SkuImageMapper;
import com.doyens.gmall.product.mapper.SkuInfoMapper;
import com.doyens.gmall.product.mapper.SkuSaleAttrValueMapper;
import com.doyens.gmall.product.service.SkuManageService;
import lombok.SneakyThrows;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class SkuManageServiceImpl implements SkuManageService {
    @Autowired
    private SkuInfoMapper skuInfoMapper;
    @Autowired
    private SkuAttrValueMapper skuAttrValueMapper;
    @Autowired
    private SkuSaleAttrValueMapper skuSaleAttrValueMapper;
    @Autowired
    private SkuImageMapper skuImageMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveSkuInfo(SkuInfo skuInfo) {
        //保存sku_info
        //sku_id保存后会设置到skuInfo对象中
        skuInfoMapper.insert(skuInfo);
        //从skuInfo对象中，取平台属性值的集合，遍历插入sku_attr_value表
        List<SkuAttrValue> skuAttrValueList = skuInfo.getSkuAttrValueList();
        for (SkuAttrValue skuAttrValue : skuAttrValueList) {
            //手动设置skuId
            skuAttrValue.setSkuId(skuInfo.getId());
            skuAttrValueMapper.insert(skuAttrValue);
        }
        //从skuInfo对象中，取销售属性值集合，遍历插入sku_sale_attr_value表
        List<SkuSaleAttrValue> skuSaleAttrValueList = skuInfo.getSkuSaleAttrValueList();
        for (SkuSaleAttrValue skuSaleAttrValue : skuSaleAttrValueList) {
            //手动设置skuid
            skuSaleAttrValue.setSkuId(skuInfo.getId());
            //手动设置spuid
            skuSaleAttrValue.setSpuId(skuInfo.getSpuId());
            skuSaleAttrValueMapper.insert(skuSaleAttrValue);
        }
        //从skuInfo对象中获取sku图片集合，遍历插入sku_image表
        List<SkuImage> skuImageList = skuInfo.getSkuImageList();
        for (SkuImage skuImage : skuImageList) {
            //手动设置skuid
            skuImage.setSkuId(skuInfo.getId());
            skuImageMapper.insert(skuImage);
        }

    }

    @Override
    @GmallCache(prefix = "sku:page:")
    public IPage<SkuInfo> getPage(Long page, Long limit) {
        Page<SkuInfo> page1 = new Page<>(page,limit);
        IPage<SkuInfo> iPage = skuInfoMapper.selectPage(page1, null);
        return iPage;
    }

    @Override
    public void onSale(Long skuId) {
        SkuInfo skuInfo = new SkuInfo();
        skuInfo.setId(skuId);
        skuInfo.setIsSale(1);
        //mp只会修改不为空的属性
        skuInfoMapper.updateById(skuInfo);
    }

    @Override
    public void cancelSale(Long skuId) {
        SkuInfo skuInfo = new SkuInfo();
        skuInfo.setId(skuId);
        skuInfo.setIsSale(0);
        //mp只会修改不为空的属性
        skuInfoMapper.updateById(skuInfo);
    }

    @Override
    public SkuInfo getSkuInfo(Long skuId){
//        SkuInfo skuInfo = getSkuInfoRedis(skuId);
        SkuInfo skuInfo = getSkuInfoRedisson(skuId);
        return skuInfo;
    }

    //从数据库查询sku的信息
    public SkuInfo getSkuInfoDb(Long skuId) {
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
        //根据skuId查询图片信息(sku_image)
        QueryWrapper<SkuImage> skuImageQueryWrapper = new QueryWrapper<>();
        skuImageQueryWrapper.eq("sku_id",skuId);
        List<SkuImage> skuImages = skuImageMapper.selectList(skuImageQueryWrapper);
        skuInfo.setSkuImageList(skuImages);
        return skuInfo;
    }

    //通过redisson完成分布式锁+redis缓存
    @SneakyThrows
    public SkuInfo getSkuInfoRedisson(Long skuId) {
        //从redis中获取缓存数据
        String skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);
        if(skuInfo==null) {
            // redis中没有数据，
            // 加锁，
            String lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKULOCK_SUFFIX;
            String uuid = UUID.randomUUID().toString();
            RLock lock = redissonClient.getLock(lockKey);
            boolean tryLock = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
            if(tryLock) {
                //加锁成功
                // 去数据库查询
                SkuInfo skuInfoDb = getSkuInfoDb(skuId);
                if(skuInfoDb==null) {
                    //查询结果是null
                    //存null到redis中，解决缓存穿透问题
                    redisTemplate.opsForValue().set(skuKey,null,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                    //返回null，没查询到数据
                    return null;
                }else {
                    //查询结果不是null
                    //把数据存入redis中
                    redisTemplate.opsForValue().set(skuKey,skuInfoDb,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
                    //释放锁
                    lock.unlock();
                    //返回数据
                    return skuInfoDb;
                }
            }else {
                //加锁失败
                //睡一觉
                Thread.sleep(300);
                // 再重试
                getSkuInfoRedisson(skuId);
            }
        }else {
            //redis中有数据，直接返回
            return skuInfo;
        }
        return getSkuInfoDb(skuId);
    }
    //从redis中获取sku的信息
    @SneakyThrows
    public SkuInfo getSkuInfoRedis(Long skuId){
        //存sku的key  sku:44:info
        String skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        //通过key去redis中查询当前数据是否有缓存
        SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);
        if(skuInfo==null){
            //redis中没有这个sku
            //redis中没有数据，要去查询数据库的时候，再上锁
            //分布式锁的key sku:44:key
            String lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKULOCK_SUFFIX;
            String uuid = UUID.randomUUID().toString();
            //尝试向redis中保存一个k-v，如果redis中没有，就保存成功，也就是上锁成功
            Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
            if(lock){
                //上锁成功，查询数据库
                SkuInfo skuInfoDb = getSkuInfoDb(skuId);
                //缓存穿透(数据库没这条数据，存到redis中也存null)
                if(skuInfoDb==null){
                    redisTemplate.opsForValue().set(skuKey,skuInfoDb,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
                    return skuInfoDb;
                }
                //数据库中有数据，把数据存入redis中，再返回数据
                //缓存雪崩,失效时间加点料
                Random random = new Random(System.currentTimeMillis());
                int i = random.nextInt(100);
                redisTemplate.opsForValue().set(skuKey,skuInfoDb,RedisConst.SKUKEY_TIMEOUT+i,TimeUnit.SECONDS);
                //释放锁，把redis中的lockKey删掉，使用lua脚本完成原子性操作  一致性  隔离性(事务的隔离级别)  持久性
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(script);
                redisScript.setResultType(Long.class);
                redisTemplate.execute(redisScript, Arrays.asList(lockKey),uuid);
                return skuInfoDb;
            }else {
                //上锁失败。等待重试
                Thread.sleep(300);
                getSkuInfoRedis(skuId);
            }
        }else{
            //redis中有sku
            return skuInfo;
        }

        return  getSkuInfoDb(skuId);
    }



    @Override
    public BigDecimal getSkuPrice(Long skuId) {
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
        if(skuInfo==null){
            return new BigDecimal("0");
        }
        return skuInfo.getPrice();
    }


    @Override
    @GmallCache(prefix = "sku:value")
    public Map getSkuValueIdsMap(Long spuId) {
        List<Map> mapList =skuSaleAttrValueMapper.getSkuValueIdsMap(spuId);
        Map mapResult = new HashMap();
        mapList.forEach(map -> {
            Object sku_id =  map.get("sku_id");
            Object value_ids = map.get("value_ids");
            mapResult.put(value_ids,sku_id);
        });
        return mapResult;
    }
}
